#!/bin/sh
### BEGIN INIT INFO
# Provides:          clickhouse-server
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Required-Start:    $network
# Required-Stop:     $network
# Short-Description: Yandex clickhouse-server daemon
### END INIT INFO

CLICKHOUSE_USER=clickhouse
CLICKHOUSE_GROUP=${CLICKHOUSE_USER}
SHELL=/bin/bash
PROGRAM=clickhouse-server
CLICKHOUSE_GENERIC_PROGRAM=clickhouse
CLICKHOUSE_PROGRAM_ENV=""
EXTRACT_FROM_CONFIG=${CLICKHOUSE_GENERIC_PROGRAM}-extract-from-config
CLICKHOUSE_CONFDIR=/etc/$PROGRAM
CLICKHOUSE_LOGDIR=/var/log/clickhouse-server
CLICKHOUSE_LOGDIR_USER=root
CLICKHOUSE_DATADIR_OLD=/opt/clickhouse
CLICKHOUSE_DATADIR=/var/lib/clickhouse
if [ -d "/var/lock" ]; then
    LOCALSTATEDIR=/var/lock
else
    LOCALSTATEDIR=/run/lock
fi

if [ ! -d "$LOCALSTATEDIR" ]; then
    mkdir -p "$LOCALSTATEDIR"
fi

CLICKHOUSE_BINDIR=/usr/bin
CLICKHOUSE_CRONFILE=/etc/cron.d/clickhouse-server
CLICKHOUSE_CONFIG=$CLICKHOUSE_CONFDIR/config.xml
LOCKFILE=$LOCALSTATEDIR/$PROGRAM
RETVAL=0
CLICKHOUSE_PIDDIR=/var/run/$PROGRAM
CLICKHOUSE_PIDFILE="$CLICKHOUSE_PIDDIR/$PROGRAM.pid"
# CLICKHOUSE_STOP_TIMEOUT=60 # Disabled by default. Place to /etc/default/clickhouse if you need.

# Some systems lack "flock"
command -v flock >/dev/null && FLOCK=flock

# Override defaults from optional config file
test -f /etc/default/clickhouse && . /etc/default/clickhouse

# On x86_64, check for required instruction set.
if uname -mpi | grep -q 'x86_64'; then
    if ! grep -q 'sse4_2' /proc/cpuinfo; then
        # On KVM, cpuinfo could falsely not report SSE 4.2 support, so skip the check.
        if ! grep -q 'Common KVM processor' /proc/cpuinfo; then

            # Some other VMs also report wrong flags in cpuinfo.
            # Tricky way to test for instruction set:
            #  create temporary binary and run it;
            #  if it get caught illegal instruction signal,
            #  then required instruction set is not supported really.
            #
            # Generated this way:
            # gcc -xc -Os -static -nostdlib - <<< 'void _start() { __asm__("pcmpgtq %%xmm0, %%xmm1; mov $0x3c, %%rax; xor %%rdi, %%rdi; syscall":::"memory"); }' && strip -R .note.gnu.build-id -R .comment -R .eh_frame -s ./a.out && gzip -c -9 ./a.out | base64 -w0; echo

            if ! (echo -n 'H4sICAwAW1cCA2Eub3V0AKt39XFjYmRkgAEmBjsGEI+H0QHMd4CKGyCUAMUsGJiBJDNQNUiYlQEZOKDQclB9cnD9CmCSBYqJBRxQOvBpSQobGfqIAWn8FuYnPI4fsAGyPQz/87MeZtArziguKSpJTGLQK0mtKGGgGHADMSgoYH6AhTMPNHyE0NQzYuEzYzEXFr6CBPQDANAsXKTwAQAA' | base64 -d | gzip -d > /tmp/clickhouse_test_sse42 && chmod a+x /tmp/clickhouse_test_sse42 && /tmp/clickhouse_test_sse42); then
                echo 'Warning! SSE 4.2 instruction set is not supported'
                #exit 3
            fi
        fi
    fi
fi


SUPPORTED_COMMANDS="{start|stop|status|restart|forcestop|forcerestart|reload|condstart|condstop|condrestart|condreload|initdb}"
is_supported_command()
{
    echo "$SUPPORTED_COMMANDS" | grep -E "(\{|\|)$1(\||})" &> /dev/null
}


is_running()
{
    [ -r "$CLICKHOUSE_PIDFILE" ] && pgrep -s $(cat "$CLICKHOUSE_PIDFILE") 1> /dev/null 2> /dev/null
}


wait_for_done()
{
    timeout=$1
    attempts=0
    while is_running; do
        attempts=$(($attempts + 1))
        if [ -n "$timeout" ] && [ $attempts -gt $timeout ]; then
            return 1
        fi
        sleep 1
    done
}


die()
{
    echo $1 >&2
    exit 1
}


# Check that configuration file is Ok.
check_config()
{
    if [ -x "$CLICKHOUSE_BINDIR/$EXTRACT_FROM_CONFIG" ]; then
        su -s $SHELL ${CLICKHOUSE_USER} -c "$CLICKHOUSE_BINDIR/$EXTRACT_FROM_CONFIG --config-file=\"$CLICKHOUSE_CONFIG\" --key=path" >/dev/null || die "Configuration file ${CLICKHOUSE_CONFIG} doesn't parse successfully. Won't restart server. You may use forcerestart if you are sure.";
    fi
}


initdb()
{
    if [ -x "$CLICKHOUSE_BINDIR/$EXTRACT_FROM_CONFIG" ]; then
        CLICKHOUSE_DATADIR_FROM_CONFIG=$(su -s $SHELL ${CLICKHOUSE_USER} -c "$CLICKHOUSE_BINDIR/$EXTRACT_FROM_CONFIG --config-file=\"$CLICKHOUSE_CONFIG\" --key=path")
        if [ "(" "$?" -ne "0" ")" -o "(" -z "${CLICKHOUSE_DATADIR_FROM_CONFIG}" ")" ]; then
            die "Cannot obtain value of path from config file: ${CLICKHOUSE_CONFIG}";
        fi
        echo "Path to data directory in ${CLICKHOUSE_CONFIG}: ${CLICKHOUSE_DATADIR_FROM_CONFIG}"
    else
        CLICKHOUSE_DATADIR_FROM_CONFIG=$CLICKHOUSE_DATADIR
    fi

    if ! getent passwd ${CLICKHOUSE_USER} >/dev/null; then
        echo "Can't chown to non-existing user ${CLICKHOUSE_USER}"
        return
    fi
    if ! getent group ${CLICKHOUSE_GROUP} >/dev/null; then
        echo "Can't chown to non-existing group ${CLICKHOUSE_GROUP}"
        return
    fi

    if ! $(su -s $SHELL ${CLICKHOUSE_USER} -c "test -r ${CLICKHOUSE_CONFIG}"); then
        echo "Warning! clickhouse config [${CLICKHOUSE_CONFIG}] not readable by user [${CLICKHOUSE_USER}]"
    fi

    if ! $(su -s $SHELL ${CLICKHOUSE_USER} -c "test -O \"${CLICKHOUSE_DATADIR_FROM_CONFIG}\" && test -G \"${CLICKHOUSE_DATADIR_FROM_CONFIG}\""); then
        if [ $(dirname "${CLICKHOUSE_DATADIR_FROM_CONFIG}") = "/" ]; then
            echo "Directory ${CLICKHOUSE_DATADIR_FROM_CONFIG} seems too dangerous to chown."
        else
            if [ ! -e "${CLICKHOUSE_DATADIR_FROM_CONFIG}" ]; then
                echo "Creating directory ${CLICKHOUSE_DATADIR_FROM_CONFIG}"
                mkdir -p "${CLICKHOUSE_DATADIR_FROM_CONFIG}"
            fi

            echo "Changing owner of [${CLICKHOUSE_DATADIR_FROM_CONFIG}] to [${CLICKHOUSE_USER}:${CLICKHOUSE_GROUP}]"
            chown -R ${CLICKHOUSE_USER}:${CLICKHOUSE_GROUP} "${CLICKHOUSE_DATADIR_FROM_CONFIG}"
        fi
    fi

    if ! $(su -s $SHELL ${CLICKHOUSE_USER} -c "test -w ${CLICKHOUSE_LOGDIR}"); then
        echo "Changing owner of [${CLICKHOUSE_LOGDIR}/*] to [${CLICKHOUSE_USER}:${CLICKHOUSE_GROUP}]"
        chown -R ${CLICKHOUSE_USER}:${CLICKHOUSE_GROUP} ${CLICKHOUSE_LOGDIR}/*
        echo "Changing owner of [${CLICKHOUSE_LOGDIR}] to [${CLICKHOUSE_LOGDIR_USER}:${CLICKHOUSE_GROUP}]"
        chown ${CLICKHOUSE_LOGDIR_USER}:${CLICKHOUSE_GROUP} ${CLICKHOUSE_LOGDIR}
    fi
}


start()
{
    [ -x $CLICKHOUSE_BINDIR/$PROGRAM ] || exit 0
    local EXIT_STATUS
    EXIT_STATUS=0

    echo -n "Start $PROGRAM service: "

    if is_running; then
        echo -n "already running "
        EXIT_STATUS=1
    else
        ulimit -n 262144
        mkdir -p $CLICKHOUSE_PIDDIR
        chown -R $CLICKHOUSE_USER:$CLICKHOUSE_GROUP $CLICKHOUSE_PIDDIR
        initdb
        if ! is_running; then
            # Lock should not be held while running child process, so we release the lock. Note: obviously, there is race condition.
            # But clickhouse-server has protection from simultaneous runs with same data directory.
            su -s $SHELL ${CLICKHOUSE_USER} -c "$FLOCK -u 9; $CLICKHOUSE_PROGRAM_ENV exec -a \"$PROGRAM\" \"$CLICKHOUSE_BINDIR/$PROGRAM\" --daemon --pid-file=\"$CLICKHOUSE_PIDFILE\" --config-file=\"$CLICKHOUSE_CONFIG\""
            EXIT_STATUS=$?
            if [ $EXIT_STATUS -ne 0 ]; then
                break
            fi
        fi
    fi

    if [ $EXIT_STATUS -eq 0 ]; then
        attempts=0
        while ! is_running && [ $attempts -le ${CLICKHOUSE_START_TIMEOUT:=10} ]; do
            attempts=$(($attempts + 1))
            sleep 1
        done
        if is_running; then
            echo "DONE"
        else
            echo "UNKNOWN"
        fi
    else
        echo "FAILED"
    fi

    return $EXIT_STATUS
}


stop()
{
    #local EXIT_STATUS
    EXIT_STATUS=0

    if [ -f $CLICKHOUSE_PIDFILE ]; then

        echo -n "Stop $PROGRAM service: "

        kill -TERM $(cat "$CLICKHOUSE_PIDFILE")

        if ! wait_for_done ${CLICKHOUSE_STOP_TIMEOUT}; then
            EXIT_STATUS=2
            echo "TIMEOUT"
        else
            echo "DONE"
        fi

    fi
    return $EXIT_STATUS
}


restart()
{
    check_config
    if stop; then
        if start; then
            return 0
        fi
    fi
    return 1
}


forcestop()
{
    local EXIT_STATUS
    EXIT_STATUS=0

    echo -n "Stop forcefully $PROGRAM service: "

    kill -KILL $(cat "$CLICKHOUSE_PIDFILE")

    wait_for_done

    echo "DONE"
    return $EXIT_STATUS
}


service_or_func()
{
    if [ -x "/bin/systemctl" ] && [ -f /etc/systemd/system/clickhouse-server.service ] && [ -d /run/systemd/system ]; then
        service $PROGRAM $1
    else
        $1
    fi
}

forcerestart()
{
    forcestop
    # Should not use 'start' function if systemd active
    service_or_func start
}

use_cron()
{
    # 1. running systemd
    if [ -x "/bin/systemctl" ] && [ -f /etc/systemd/system/clickhouse-server.service ] && [ -d /run/systemd/system ]; then
        return 1
    fi
    # 2. disabled by config
    if [ -z "$CLICKHOUSE_CRONFILE" ]; then
        return 2
    fi
    return 0
}

enable_cron()
{
    use_cron && sed -i 's/^#*//' "$CLICKHOUSE_CRONFILE"
}


disable_cron()
{
    use_cron && sed -i 's/^#*/#/' "$CLICKHOUSE_CRONFILE"
}


is_cron_disabled()
{
    use_cron || return 0

    # Assumes that either no lines are commented or all lines are commented.
    # Also please note, that currently cron file for ClickHouse has only one line (but some time ago there was more).
    grep -q -E '^#' "$CLICKHOUSE_CRONFILE";
}


main()
{
    # See how we were called.
    EXIT_STATUS=0
    case "$1" in
    start)
        start && enable_cron
        ;;
    stop)
        # disable_cron returns false if cron disabled (with systemd) - not checking return status
        disable_cron
        stop
        ;;
    restart)
        restart && enable_cron
        ;;
    forcestop)
        disable_cron
        forcestop
        ;;
    forcerestart)
        forcerestart && enable_cron
        ;;
    reload)
        restart
        ;;
    condstart)
        is_running || service_or_func start
        ;;
    condstop)
        is_running && service_or_func stop
        ;;
    condrestart)
        is_running && service_or_func restart
        ;;
    condreload)
        is_running && service_or_func restart
        ;;
    initdb)
        initdb
        ;;
    enable_cron)
        enable_cron
        ;;
    disable_cron)
        disable_cron
        ;;
    *)
        echo "Usage: $0 $SUPPORTED_COMMANDS"
        exit 2
        ;;
    esac

    exit $EXIT_STATUS
}


status()
{
    if is_running; then
        echo "$PROGRAM service is running"
        exit 0
    else
        if is_cron_disabled; then
            echo "$PROGRAM service is stopped";
        else
            echo "$PROGRAM: process unexpectedly terminated"
        fi
        exit 3
    fi
}


# Running commands without need of locking
case "$1" in
status)
    status
    ;;
esac


(
    if $FLOCK -n 9; then
        main "$@"
    else
        echo "Init script is already running" && exit 1
    fi
) 9> $LOCKFILE
